import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;

public class BerkeleyDBWrapper {
	private Environment env;
	private Database db;
	
	/**
	 * This is the constructor for the BerkeleyDBWrapper Class.  This class is meant to isolate all of the
	 * specifics of the storing of data in a BerkeleyDB.  By creating this class, its methods allow the user
	 * to use simple "put" and "get" methods, much like with a hashmap.  
	 * @param pathToDir  This is the String representation of the desired FilePath in which to store the database.
	 * @throws DatabaseException This is thrown when there is an Exception generated by BerekelyDB
	 * @throws FileNotFoundException This is thrown if the FilePath String given does not exist
	 */
	public BerkeleyDBWrapper(String pathToDir) throws DatabaseException, FileNotFoundException {
		EnvironmentConfig envConfig = new EnvironmentConfig();
        envConfig.setTransactional(true);
        envConfig.setAllowCreate(true);
        env = new Environment(new File(pathToDir), envConfig);
        DatabaseConfig databaseConfig = new DatabaseConfig();
		databaseConfig.setAllowCreate(true);
		databaseConfig.setDeferredWrite(false);
		databaseConfig.setTransactional(true);
		db = env.openDatabase(null, "IndexerCache", databaseConfig);
	}
	
	//the 'get' methods follow - they are formatted to specifically return either a String, Double, raw Object,
	//byte array, or ArrayList<String>

	/**
	 * This method returns a String value for the given key.
	 * @param  key  The String key corresponding with the desired String value
	 * @return      the String value that corresponds to the key
	 */
	public String getDBStringVal(String key) throws DatabaseException {
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key.getBytes());
		DatabaseEntry dbdata = new DatabaseEntry();
		db.get(null, dbkey, dbdata, null);
		txn.commit();
		return dbdata.getData() == null ? "" : new String(dbdata.getData());
	}
	
	/**
	 * This method returns a Double value for the given key.  This is useful for 
	 * getting metric data
	 * @param  key  The String key corresponding with the desired Double value
	 * @return      the Double value that corresponds to the key
	 */
	public Double getDBDoubleVal(String key) throws DatabaseException{
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key.getBytes());
		DatabaseEntry dbdata = new DatabaseEntry();
		db.get(null, dbkey, dbdata, null);
		txn.commit();
		if (dbdata.getData() == null || dbdata.getData().toString() == ""){
			System.out.println("The key "+key+" returned a null DB value!");
			Integer i = Integer.MIN_VALUE;
			return i.doubleValue();
		}else{
			String temp = new String(dbdata.getData());
			Integer tempInt = Integer.parseInt(temp);
			return tempInt.doubleValue();
		}
	}
	
	/**
	 * This method returns a Object value for the given key.
	 * @param  key  The String key corresponding with the desired Object value
	 * @return      the Object value that corresponds to the key
	 */
	public byte[] getDBObjVal(String key) throws DatabaseException{
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key.getBytes());
		DatabaseEntry dbdata = new DatabaseEntry();
		db.get(null, dbkey, dbdata, null);
		txn.commit();
		byte[] toReturn = dbdata.getData();
		return toReturn;
	}
	
	/**
	 * This method returns an ArrayList<String> value for the given key.  This is used to return
	 * stored date and mapping ArrayList<String>.
	 * @param  key  The String key corresponding with the desired ArrayList<String> value
	 * @return      the ArrayList<String> value that corresponds to the key
	 */
	public ArrayList<String> getDBArrayListVal(String key) throws DatabaseException{
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key.getBytes());
		DatabaseEntry dbdata = new DatabaseEntry();
		db.get(null, dbkey, dbdata, null);
		txn.commit();
		byte[] byteData = dbdata.getData();
 		ArrayList<String> back = new ArrayList<String>();
 		String temp = "";
 		for (int i=0; i<byteData.length ;i++){
 			Character c = new Character((char) byteData[i]);
 			char m = c.charValue();
 			if(m!='['&&m!=']'&&m!=','&&m!=' '){
 				if(temp==""){
 					temp = c.toString();
 				}else{
 	 				temp = temp.concat(c.toString());
 				}
 			}else if (c == ',' || c == ']'){
 				back.add(temp);
 				temp = "";
 			}
 		}
		return back;
	}
	
	/**
	 * This method returns a byte array value for the given key.  This is the 
	 * raw data
	 * @param  key  The String key corresponding with the desired byte array value
	 * @return      the byte array value that corresponds to the key
	 */
	public byte[] getDBValBytes(byte[] key) throws DatabaseException {
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key);
		DatabaseEntry dbdata = new DatabaseEntry();
		db.get(null, dbkey, dbdata, null);
		txn.commit();
		return dbdata.getData();
	}
	
	//the put methods follow - there are less of these, and Object types don't need to be specifically dealt with here
	
	/**
	 * This method stores a simple value in the database for the given String key.  
	 * The simple value can be any primitive or String.  This is primarily used to store
	 * String-to-String key-value pairs and String-to-Double key-value pairs.  Since the
	 * value is being converted to a byte array, the specific type of primitive makes no difference.
	 * @param  key  The String key corresponding with the desired "simple" value
	 * @param  val  The String value to be stored in the database - this could be the String representation of a Double
	 */
	public void putDBSimpleVal(String key, String val) throws DatabaseException {
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key.getBytes());
		DatabaseEntry dbdata = new DatabaseEntry(val.getBytes());
		db.put(null, dbkey, dbdata);
		txn.commit();
	}
	
	/**
	 * This method stores an Object value in the database for the given String key.  
	 * This can be any type of Object, but is usually an ArrayList<String> for our purposes.
	 * @param  key  The String key corresponding with the desired Object value
	 * @param  val  The Object value to be stored in the database - this is usually an ArrayList<String>
	 */
	public void putDBObjVal(String key, Object val){
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		String convert = val.toString();
		DatabaseEntry dbkey = new DatabaseEntry(key.getBytes());
		DatabaseEntry dbdata = new DatabaseEntry(convert.getBytes());
		db.put(null, dbkey, dbdata);
		txn.commit();
	}
	
	/**
	 * This method stores a byte array value in the database for the given byte array key.  
	 * This is the most modular of the put methods, but this method should never really be used,
	 * since each specific type of data is dealt with specifically.
	 * @param  key  The byte array key corresponding with the desired byte array value
	 * @param  val  The byte array value to be stored in the database
	 */
	public void putDBValBytes(byte[] key, byte[] val) throws DatabaseException {
		Transaction txn = env.beginTransaction(null, null);
		//init the two DatabaseEntry objs used for dbkey and dbdata
		DatabaseEntry dbkey = new DatabaseEntry(key);
		DatabaseEntry dbdata = new DatabaseEntry(val);
		db.put(null, dbkey, dbdata);
		txn.commit();
	}
	
}
